解析HTTP请求报文(GET、POST)

您所在的位置:网站首页 get post head 解析HTTP请求报文(GET、POST)

解析HTTP请求报文(GET、POST)

2024-07-13 01:02| 来源: 网络整理| 查看: 265

目的:

一个WEB服务器需要解析客户端(浏览器)发来的请求,两种常见的请求方式是GET和POST。

GET的请求格式:

GET请求没有请求体只有请求头GET请求的请求参数放在URL后加上一个"?"的后面,参数以key=value的形式传递,参数与参数之间使用"&"进行连接。 GET /signin?next=%2F HTTP/2\r\n Host: www.zhihu.com\r\n User-Agent: Mozilla/5.0\r\n Accept: */*\r\n Accept-Language: zh-CN\r\n Accept-Encoding: gzip, deflate\r\n Connection: keep-alive\r\n Upgrade-Insecure-Requests: 1\r\n Cache-Control: max-age=0\r\n TE: trailers\r\n \r\n 请求头中每一行的后面都要加"\r\n"结尾;第一行是状态行,分别是请求方法(GET)、请求路径(/signin?next=%2F)、协议版本(HTTP/2);其余所有行均以XXX: XXXX的格式表示;最后需要一个"\r\n"的空行作为请求头结束的标志。

POST的请求格式:

POST请求传送的数据放在请求体中;POST请求的请求参数放在请求体中,由请求头中的"Content-Type"字段决定其格式;如果是"Content-Type: application/x-www-form-urlencoded",则请求参数以key=value的形式传递,参数与参数之间使用"&"进行连接如果是"Content-Type: multipart/form-data",则使用boundary(分割线)充当参数与参数之间的连接(相当于&) POST /login HTTP/1.1\r\n Host: 127.0.0.1:8888\r\n User-Agent: Mozilla/5.0\r\n Accept: */*\r\n Accept-Language: zh-CN\r\n Accept-Encoding: gzip, deflate\r\n Content-Type: application/x-www-form-urlencoded\r\n Content-Length: 29\r\n Connection: keep-alive\r\n \r\n username=root&password=123456 请求头中每一行的后面都要加"\r\n"结尾;第一行是状态行,分别是请求方法(POST)、请求路径(/login)、协议版本(HTTP/1.1);请求头内的剩余内容均以XXX: XXXX的格式表示;请求头最后需要一个"\r\n"的空行作为结束的标志;放在请求体内的请求参数以key=value的形式传递,参数与参数之间使用"&"进行连接。

功能介绍: 

使用状态机和正则表达式完成了对HTTP请求报文的解析,支持解析GET报文和POST报文(仅限Content-Type: application/x-www-form-urlencoded)。

由于计划完成的web服务器需要实现展示主页(GET)、用户登录(POST)、用户注册(POST)、获取图片(GET)、获取视频(GET)五个功能,所以web服务器的请求解析模块满足:

若为GET请求,可根据状态行信息,完成对请求内容地址的转换,以及请求头内其他内容的提取。

若为POST请求,可根据请求参数,完成登录和注册这两个功能(登录:根据后台数据库表中的信息判断用户名与密码是否正确;注册:向后台数据库表中插入符合条件的新用户名和密码)。

状态机流程:

enum PARSE_STATE { REQUEST_LINE, HEADERS, BODY, FINISH };

如果为GET请求: REQUEST_LINE——>HEADERS——>FINISH;

如果为POST请求:REQUEST_LINE——>HEADERS——>BODY——>FINISH。  

用到的正则表达式:

 1、^([^ ]*) ([^ ]*) HTTP/([^ ]*)$        匹配状态行

 2、^([^:]*): ?(.*)$        匹配请求头内的XXX: XXXX字段

 

 3、(?!&)(.*?)=(.*?)(?=&|$)        匹配POST的请求参数

 

HttpRequest类结构        httprequest.h #ifndef HTTPREQUEST_H #define HTTPREQUEST_H #include #include #include #include #include #include #include #include "buffer.h" #include "log.h" #include "sqlconnpool.h" using std::string; class HttpRequest { public: enum PARSE_STATE//解析流程的状态 { REQUEST_LINE, HEADERS, BODY, FINISH }; HttpRequest(); ~HttpRequest()=default; bool parse(Buffer& buffer);//解析全过程 const string& getMethod() const; const string& getPath() const; const string& getVersion() const; bool isKeepAlive() const; private: void parseRequestLine(const string& line);//解析状态行 void parseHeader(const string& line);//解析请求头 void parseBody(const string& line);//解析请求体 void parsePath();//解析请求路径 void parsePost();//解析POST请求 void parseUrlencoded();//解析POST请求的请求参数 bool userVertify(const string& username,const string& password,int tag);//身份验证 PARSE_STATE state; string method; string path; string version; string body; std::unordered_map header;//存储请求头字段 std::unordered_map post; //存储POST请求参数 static const std::unordered_set DEFAULT_HTML; static const std::unordered_map DEFAULT_HTML_TAG; }; #endif // !HTTPREQUEST_H  HttpRequest类实现       httprequest.cpp #include "httprequest.h" const std::unordered_set HttpRequest::DEFAULT_HTML= {"/home","/register","/login","/video","/picture"}; const std::unordered_map HttpRequest::DEFAULT_HTML_TAG= {{"/register.html", 0},{"/login.html", 1}}; HttpRequest::HttpRequest() { Log::getInstance()->init(); init(); } void HttpRequest::init() { method=""; path=""; version=""; body=""; state = REQUEST_LINE; header.clear(); post.clear(); } bool HttpRequest::parse(Buffer& buffer) { if(buffer.readableBytes()second; if(userVertify(post["username"],post["password"],tag)) { path="/home.html"; } else { path="/error.html"; } } } } void HttpRequest::parseUrlencoded() { std::regex patten("(?!&)(.*?)=(.*?)(?=&|$)"); std::smatch match; string::const_iterator begin=body.begin(); string::const_iterator end=body.end(); while(std::regex_search(begin,end,match,patten)) { post[match[1]]=match[2]; begin=match[0].second; } } bool HttpRequest::userVertify(const string& username,const string& password,int tag) { SqlConnPool* pool = SqlConnPool::getInstance(); std::shared_ptr conn=pool->getConn(); string order1="SELECT username,password FROM user WHERE username='"+username+"' LIMIT 1"; string order2="INSERT INTO user(username, password) VALUES('"+username+"','"+password+"')"; MYSQL_RES* res=conn->query(order1); string user; string pwd; MYSQL_ROW row=nullptr; while((row=mysql_fetch_row(res))!=nullptr) { user=row[0]; pwd=row[1]; } if(tag)//登录 { if(pwd!=password)//密码错误 { LOG_ERROR("%s","Password Error"); return false; } LOG_INFO("%s Login Success",username); } else//注册 { if(!user.empty())//用户名已被使用 { LOG_ERROR("%s","Username Used"); return false; } if(!conn->update(order2))//数据库插入失败 { LOG_ERROR("%s","Insert Error"); return false; } LOG_INFO("%s Register Success",username); } mysql_free_result(res); return true; } const string& HttpRequest::getMethod() const { return method; } const string& HttpRequest::getPath() const { return path; } const string& HttpRequest::getVersion() const { return version; } bool HttpRequest::isKeepAlive() const { if(header.count("Connection")) { return header.find("Connection")->second=="keep-alive"; } return false; }

 测试程序        testHttpRequest.cpp

分别解析GET请求和POST请求,根据解析内容进行判断。

#include "httprequest.h" #include using namespace std; void testPost() { HttpRequest request; Buffer input; input.append("POST /login HTTP/1.1\r\n" "Host: 127.0.0.1:8888\r\n" "User-Agent: Mozilla/5.0\r\n" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n" "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\r\n" "Accept-Encoding: gzip, deflate\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: 29\r\n" "Connection: keep-alive\r\n" "\r\n" "username=root&password=123456"); request.parse(input); cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3